#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "scan.h"
#include "opt.h"
#include "selector.h"
#include "err.h"
#include "sub.h"

enum op_enum {
	LET,
	ADD,
	SUB,
	MUL,
	DIV,
};

struct transform {
	enum op_enum op;
	long y;
};

static struct transform *xfrm;
static size_t xfrm_allocated;
static size_t xfrm_read;

/* XXX overflow possible */
static long do_arithmetic(long x)
{
	size_t i;
	enum op_enum op;
	long y;
	for (i = 0; i < xfrm_read; i++) {
		op = xfrm[i].op;
		y = xfrm[i].y;
		switch (op) {
		case LET:
			x = y;
			break;
		case ADD:
			x += y;
			break;
		case SUB:
			x -= y;
			break;
		case MUL:
			x *= y;
			break;
		case DIV:
			x /= y;
			break;
		default:
			break;
		}
	}
	return x;
}

static scan_verdict
callback(scan_phase phase, struct tagbuf *b, struct context *ctx)
{
	if (phase == TAG && is_selected(ctx))
		flv_set_time(b, do_arithmetic(flv_time(b)));
	return OUTPUT;
}

static int strtolong(const char *s, long *l)
{
	char *end;

	errno = 0;
	*l = strtol(s, &end, 0);
	if (errno) {
		perrmsg(s);
		return -1;
	}
	if (end == s) {
		pmsgmsg("No digits were found", s);
		return -1;
	}
	if (end[0] != '\0') {
		pmsgmsg("Trailing garbage", s);
		return -1;
	}

	return 0;
}

static void push(enum op_enum op, long y)
{
	void *n;
	if (xfrm_read + 1 > xfrm_allocated) {
		if (!xfrm_allocated)
			xfrm_allocated = 8;
		else
			xfrm_allocated *= 2;
		n = realloc(xfrm, sizeof(struct transform [xfrm_allocated]));
		if (!n)
			die(1);
		xfrm = n;
	}
	xfrm[xfrm_read].op = op;
	xfrm[xfrm_read].y = y;
	xfrm_read++;
}

static int parse_transform(const struct option_def *def, const char *arg)
{
	enum op_enum op;
	long y;
	switch (def->shortname) {
	case 'l':
		op = LET;
		break;
	case 'a':
		op = ADD;
		break;
	case 's':
		op = SUB;
		break;
	case 'm':
		op = MUL;
		break;
	case 'd':
		op = DIV;
		break;
	default:
		return -1;
	}
	if (strtolong(arg, &y) < 0)
		return -1;
	if (y == 0 && op == DIV) {
		pmsg("division by zero");
		return -1;
	}
	push(op, y);
	return 0;
}

static const struct option_def transform_optdef[] = {
	{OPT_FUNC, 'l', "let", {.func = parse_transform}, "assignment", "NUMBER"},
	{OPT_FUNC, 'a', "add", {.func = parse_transform}, "addition", "NUMBER"},
	{OPT_FUNC, 's', "sub", {.func = parse_transform}, "subtraction", "NUMBER"},
	{OPT_FUNC, 'm', "mul", {.func = parse_transform}, "multiplication", "NUMBER"},
	{OPT_FUNC, 'd', "div", {.func = parse_transform}, "division", "NUMBER"},
	{.type = OPT_END},
};
static const struct option_group transform_options = {
	.name = "Transforms",
	.defs = transform_optdef,
};

static int show_help;
static int inplace;
static const struct option_def optdef[] = {
	{OPT_FLAG, 0, "help", {.flag = &show_help}, "show help", 0},
	{OPT_FLAG, 'i', "inplace", {.flag = &inplace}, "inplace edit", 0},
	{.type = OPT_GROUP, .ptr = {.group = &transform_options}},
	{.type = OPT_GROUP, .ptr = {.group = &selector_options}},
	{.type = OPT_END},
};
static const struct option_group optgroup = {.defs = optdef};

static void usage(void)
{
	printf("Usage: %s [-i] [selectors] transforms [FILE]...\n", prog);
}

int shift_main(int argc, const char *const *argv)
{
	const char *files[argc];
	const char **current;
	FILE *in, *out;
	int r;

	memset(files, 0, sizeof(files));
	if (parse_options(argc, argv, &optgroup, argc, files) < 0) {
		usage();
		return 2;
	}
	if (!inplace && parse_options(argc, argv, &optgroup, 1, files) < 0) {
		usage();
		return 2;
	}
	if (show_help) {
		usage();
		print_options(&optgroup);
		return 0;
	}

	if (!inplace) {
		in = open_file(files[0], 'r');
		out = open_file(0, 'w');

		r = scan(in, out, callback);

		close_file(in);
		close_file(out);
		free(xfrm);
		return r;
	}

	for (current = files; *current; current++) {
		in = open_file(*current, 'r');
		if (!in)
			continue;
		out = open_tmpfile(*current, 'w');
		if (!out) {
			close_file(in);
			continue;
		}
		r = scan(in, out, callback);
		close_file(in);
		close_file(out);
		if (r < 0)
			remove_tmp(*current);
		else
			replace_tmp(*current);
	}

	free(xfrm);
	return 0;
}
